#ifndef     __GPIO_H
#define     __GPIO_H

#include "./compat.h"

namespace device {

enum class GpioMode : uint8_t {
    Input = 0x00,
    Output = 0x01,
    Af = 0x02,
    Analog = 0x03,    
};

enum class GpioOtype : uint8_t {
    PushPull = 0x00,
    OpenDrain = 0x01,
};

enum class GpioPupd : uint8_t {
    Nopull = 0x00,
    Up = 0x01,
    Down = 0x02,
};

enum class GpioOspeed : uint8_t {
    O2MHz = 0x00,
    O25MHz = 0x01,
    O50MHz = 0x02,
    O100MHz = 0x03,
};

namespace helper {

template <typename T>
constexpr uint32_t Mask1(T val, uint8_t pin) {
    return static_cast<uint8_t>(val) << pin;
}

template <typename T>
constexpr uint32_t Mask2(T val, uint8_t pin) {
    return static_cast<uint8_t>(val) << (pin * 2);
}

template <typename T>
constexpr uint32_t Mask4(T val, uint8_t pin) {
    return static_cast<uint8_t>(val) << (pin * 4);
}

}

#define Port(Base)  reinterpret_cast<GPIO_TypeDef *>(Base)

template <uint32_t Base, uint32_t Pin>
struct Gpio {
    
    static inline void SetMode(GpioMode mode) {
        Port(Base)->MODER = Port(Base)->MODER & 
            (~helper::Mask2(0x03, Pin)) |
            helper::Mask2(mode, Pin);
    }

    static inline void SetPupd(GpioPupd pupd) {
        Port(Base)->PUPDR = Port(Base)->PUPDR &
            (~helper::Mask2(0x03, Pin)) |
            helper::Mask2(pupd, Pin);
    }

    static inline void SetOspeed(GpioOspeed speed) {
        Port(Base)->OSPEEDR = Port(Base)->OSPEEDR &
            (~helper::Mask2(0x03, Pin)) |
            helper::Mask2(speed, Pin);
    }

    static inline void SetOtype(GpioOtype type) {
        Port(Base)->OTYPER = Port(Base)->OTYPER &
            (~helper::Mask1(0x01, Pin)) |
            helper::Mask1(type, Pin);
    }

    static inline void SetAf(uint8_t af) {
        static_assert(Pin <= 15, "Pin <= 15");

        if constexpr (Pin < 8) {
            Port(Base)->AFR[0] = Port(Base)->AFR[0] &
                (~helper::Mask4(0x0F, Pin)) |
                helper::Mask4(af, Pin);
        } else {
            Port(Base)->AFR[1] = Port(Base)->AFR[1] &
                (~helper::Mask4(0x0F, Pin - 8)) |
                helper::Mask4(af, Pin - 8);
        }
    }

    static inline bool Read() {
        return 0x00 != (Port(Base)->IDR & helper::Mask1(0x01, Pin));
    }

    static inline void Set() {
        Port(Base)->BSRRL = helper::Mask1(0x01, Pin);
    }

    static inline void Clr() {
        Port(Base)->BSRRH = helper::Mask1(0x01, Pin);
    }

    static inline void Toggle() {
        Port(Base)->ODR ^= helper::Mask1(0x01, Pin);
    }
};

template <typename... Ts>
inline void ConfigOutput() {
	((Ts::SetMode(GpioMode::Output),
	  Ts::SetOspeed(GpioOspeed::O100MHz),
	  Ts::SetPupd(GpioPupd::Nopull),
	  Ts::SetOtype(GpioOtype::PushPull),
      Ts::Clr()), ...);
}

template <typename... Ts>
inline void ConfigOD() {
	((Ts::SetMode(GpioMode::Output),
	  Ts::SetOspeed(GpioOspeed::O100MHz),
	  Ts::SetPupd(GpioPupd::Up),
	  Ts::SetOtype(GpioOtype::OpenDrain),
      Ts::Set()), ...);
}

template <typename... Ts>
inline void ConfigAin() {
    (Ts::SetMode(GpioMode::Analog), ...);
}

template <typename... Ts>
inline void ConfigUpInput() {
    ((Ts::SetMode(GpioMode::Input),
      Ts::SetOspeed(GpioOspeed::O100MHz),
      Ts::SetPupd(GpioPupd::Up)),...);
}

template <typename... Ts>
inline void ConfigDownInput() {
    ((Ts::SetMode(GpioMode::Input),
      Ts::SetOspeed(GpioOspeed::O100MHz),
      Ts::SetPupd(GpioPupd::Down)),...);
}

template <typename... Ts>
inline void ConfigFloatInput() {
    ((Ts::SetMode(GpioMode::Input),
      Ts::SetOspeed(GpioOspeed::O100MHz),
      Ts::SetPupd(GpioPupd::Nopull)),...);
}

template <uint32_t af, typename... Ts>
inline void ConfigAf() {
    ((Ts::SetMode(GpioMode::Af),
      Ts::SetOspeed(GpioOspeed::O100MHz),
      Ts::SetPupd(GpioPupd::Up),
      Ts::SetAf(af)),...);
}

template <uint32_t Pin>
using PA = Gpio<GPIOA_BASE, Pin>;

template <uint32_t Pin>
using PB = Gpio<GPIOB_BASE, Pin>;

template <uint32_t Pin>
using PC = Gpio<GPIOC_BASE, Pin>;

template <uint32_t Pin>
using PD = Gpio<GPIOD_BASE, Pin>;

template <uint32_t Pin>
using PE = Gpio<GPIOE_BASE, Pin>;

template <uint32_t Pin>
using PF = Gpio<GPIOF_BASE, Pin>;

template <uint32_t Pin>
using PG = Gpio<GPIOG_BASE, Pin>;

}



#endif
